package cn.stu.framework.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.stu.common.dto.UsersDto;
import cn.stu.common.enums.UserDisableEnum;
import cn.stu.common.utils.ApiData;
import cn.stu.common.utils.JwtHelper;
import cn.stu.common.utils.UserHolder;
import cn.stu.framework.dto.LoginDto;
import cn.stu.framework.entity.Menu;
import cn.stu.framework.entity.Role;
import cn.stu.framework.entity.RoleMenu;
import cn.stu.framework.entity.Users;
import cn.stu.framework.mapper.MenuMapper;
import cn.stu.framework.mapper.RoleMapper;
import cn.stu.framework.mapper.RoleMenuMapper;
import cn.stu.framework.mapper.UsersMapper;
import cn.stu.framework.service.UsersService;
import cn.stu.framework.vo.LoginVo;
import cn.stu.framework.vo.UsersVo;
import cn.stu.framework.vo.rolemenu.GetMenuVo;
import cn.stu.framework.vo.routes.MenuVo;
import cn.stu.framework.vo.routes.Meta;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.AllArgsConstructor;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.cglib.beans.BeanCopier;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;

import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static cn.stu.common.core.Constants.TOKEN;
import static cn.stu.common.utils.SystemConstants.LOGIN_USER_KEY;
import static cn.stu.common.utils.SystemConstants.LOGIN_USER_TTL;


/**
 * 用户服务包括
 *
 * @author 郑志豪
 * @date 2023/09/14
 */
@Service
@AllArgsConstructor
public class UsersServiceImpl extends ServiceImpl<UsersMapper, Users> implements UsersService {

    private RoleMenuMapper rmMapper;

    private MenuMapper menuMapper;

    private RoleMapper roleMapper;

    private StringRedisTemplate stringRedisTemplate;


    /**
     * 登录
     *
     * @param users   用户
     * @param request 请求
     * @return {@link ApiData}
     */
    @Override
    public ApiData login(LoginDto users, HttpServletRequest request) {
        long start = System.currentTimeMillis();
        Users login = lambdaQuery().eq(Users::getAcc, users.getAcc()).one();
        if (login == null) {
            return ApiData.getFalse("用户名密码错误！");
        }
        if (login.getIsDisable() != 0) {
            return ApiData.getFalse("此账户已禁用，请联系管理员！");
        }
        String password = DigestUtils.md5DigestAsHex(users.getPwd().getBytes());
        long end = System.currentTimeMillis() - start;
        log.debug(String.valueOf(end));
        if (!login.getPwd().equals(password)) {
            return ApiData.getFalse("用户名密码错误！");
        }
        // 生成tokenKey
        String tokenKey = LOGIN_USER_KEY + login.getUserId();
        // 验证tokenKey是否存在
        String token = stringRedisTemplate.opsForValue().get(tokenKey);
        if (StrUtil.isNotBlank(token)) {
            if (!Objects.equals(JwtHelper.getSessionId(token), request.getSession().getId())) {
                //存在，清除原有token
                stringRedisTemplate.delete(tokenKey);
            }
        } else {
            // 生成token，作为登录令牌
            token = JwtHelper.createToken(login.getUserId(), request.getSession().getId());
            // 存储至redis
            stringRedisTemplate.opsForValue().set(tokenKey, token);
            // 设置token有效期
            stringRedisTemplate.expire(tokenKey, LOGIN_USER_TTL, TimeUnit.SECONDS);
        }
        //封装数据
        UsersVo info = baseMapper.userMsg(login.getUserId());
        LoginVo loginVo = new LoginVo();
        loginVo.setUserId(login.getUserId());
        loginVo.setName(info.getName());
        loginVo.setUserImage(info.getUserImage());
        loginVo.setToken(token);
        UsersDto user = new UsersDto();
        user.setUserId(info.getUserId());
        user.setSessionId(request.getSession().getId());
        //保存用户信息到 ThreadLocal
        UserHolder.saveUser(user);
        return ApiData.getTrue("登录成功！", loginVo);
    }

    /**
     * 设置角色
     *
     * @param userId 用户标识
     * @param roleId 角色标识
     * @return {@link ApiData}
     */
    @Override
    @Caching(evict = {@CacheEvict(value = "UsersDetailList", allEntries = true, beforeInvocation = true), @CacheEvict(value = "routes", key = "'roleId:'+#roleId", beforeInvocation = true)})
    public ApiData setRole(Long userId, Long roleId) {
        boolean userExists = lambdaQuery().eq(Users::getUserId, userId).exists();
        if (!userExists) {
            return ApiData.getFalse("用户不存在!");
        }
        if (userId == 1) {
            return ApiData.getFalse("超管权限不可修改！");
        }
        LambdaQueryWrapper<Role> roleQuery = new LambdaQueryWrapper<>();
        roleQuery.eq(Role::getRoleId, roleId);
        boolean roleExists = roleMapper.exists(roleQuery);
        if (!roleExists) {
            return ApiData.getFalse("该角色不存在！");
        }
        baseMapper.setRoleInfo(userId, roleId, UserHolder.getUser().getUserId());
        return ApiData.getTrue("分配成功!");
    }


    /**
     * 注销
     *
     * @param request 请求
     * @return {@link ApiData}
     */
    @Override
    public ApiData logout(HttpServletRequest request) {
        String token = request.getHeader(TOKEN);
        Long userId = JwtHelper.getUserId(token);
        String tokenKey = LOGIN_USER_KEY + userId;
        stringRedisTemplate.delete(tokenKey);
        return ApiData.getTrue("登出成功！");
    }

    /**
     * 获取路由
     *
     * @param roleId 角色标识
     * @return {@link ApiData}
     */
    @Override
    @Cacheable(value = "routes", key = "'roleId:'+#roleId", sync = true)
    public ApiData getRoutes(Long roleId) {
        Long userId = UserHolder.getUser().getUserId();
        Users user = lambdaQuery().eq(Users::getUserId, userId).eq(Users::getIsDisable, UserDisableEnum.ENABLE.getValue()).one();
        if (user == null) {
            return ApiData.getFalse("用户状态异常");
        }
        if (user.getRoleId() == null) {
            return ApiData.getTrue("获取成功", null);
        }
        List<GetMenuVo> gMvo = dynamicRoute(user.getRoleId());
        return ApiData.getTrue("获取成功", gMvo);
    }

    /**
     * 动态路由
     *
     * @param roleId 角色标识
     * @return {@link List}<{@link GetMenuVo}>
     */
    public List<GetMenuVo> dynamicRoute(Long roleId) {
        // 根据角色id查出所拥有的权限的菜单id生成动态路由表
        LambdaQueryWrapper<RoleMenu> rmWrapper = new LambdaQueryWrapper<>();
        rmWrapper.eq(RoleMenu::getRoleId, roleId);
        // 查询当前角色所有权限
        List<RoleMenu> rmList = rmMapper.selectList(rmWrapper);
        List<Long> midAll = rmList.stream().map(RoleMenu::getMenuId).distinct().sorted().collect(Collectors.toList());
        // 取出所有权限Id并查询对应菜单信息
        List<Menu> menusAll = getMenuList(midAll);
        // 获取主菜单Id列表
        List<Long> mIds = menusAll.stream().map(Menu::getPid).distinct().collect(Collectors.toList());
        // 根据主菜单Id查询主菜单信息，并将其加入菜单列表
        List<Menu> menusMain = getMenuList(mIds);
        //不为空则取并集
        if (CollUtil.isNotEmpty(menusMain)) {
            menusAll.addAll(menusMain);
        }
        List<GetMenuVo> gMvo = new ArrayList<>();
        // 遍历查询并封装数据
        for (Menu m : menusAll) {
            GetMenuVo rmv = new GetMenuVo();
            Long mainId;
            if (m.getPid() == 0) {
                mainId = m.getMenuId();
                // 查询主菜单数据
                BeanCopier menuToGetMenuVo = BeanCopier.create(Menu.class, GetMenuVo.class, false);
                menuToGetMenuVo.copy(m, rmv, null);
                rmv.setMeta(new Meta(m.getTitle(), m.getIcon()));
            } else {
                continue;
            }
            // 打包子菜单数据
            ArrayList<MenuVo> menuVos = new ArrayList<>();
            for (Menu child : menusAll) {
                if (Objects.equals(child.getPid(), mainId)) {
                    BeanCopier menuToMenuVo = BeanCopier.create(Menu.class, MenuVo.class, false);
                    MenuVo menuVo = new MenuVo();
                    menuToMenuVo.copy(child, menuVo, null);
                    menuVo.setMeta(new Meta(child.getTitle(), child.getIcon()));
                    menuVos.add(menuVo);
                }
            }
            rmv.setChildren(menuVos);
            gMvo.add(rmv);
        }
        return gMvo;
    }

    /**
     * 获取菜单列表
     *
     * @param mIds M ID
     * @return {@link List}<{@link Menu}>
     */
    private List<Menu> getMenuList(List<Long> mIds) {
        LambdaQueryWrapper<Menu> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.in(Menu::getMenuId, mIds);
        return menuMapper.selectList(queryWrapper);
    }
}